//
//  ServerConnectionController.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/02/06.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "ServerConnectionController.h"
#import "SolarSeekEngine.h"
#import "Message.h"
#import "Connections.h"
#import "TimelyInformation.h"
#import "ConnectionsIdentifier.h"
#import "Peers.h"


@interface ServerConnectionController (Private)
- (void)pingServer;
- (void)updatePingTimerInterval;

- (void)dispatchMessage:(Message *)m;

- (void)receiveMessageLogin:(Message *)m;
- (void)receiveMessageAdministrativeMessage:(Message *)m;
- (void)receiveMessagePrivilegesRemaining:(Message *)m;
- (void)receiveMessageSecondsBeforePingChildren:(Message *)m;

@end

@implementation ServerConnectionController
- (id)init {
	[NSException raise:@"InvalidArgumentException" format:@"[ServerConnectionController init] is not a valid initializer."];
	[self release];
	return nil;
}

- (id)initWithEngine:(SolarSeekEngine *)engine {
	if(self = [super init]) {
		_engine = engine;
		_privileged = nil;
		_greeting = nil;
		_error = nil;
		_admin = nil;
		_serverConnection = [[engine serverConnection] retain];
		secondsDelayPing = 60;
		_pingTimer = nil;
		_usernameLoggedIn = nil;
		_selectorRetrieverPassword = @selector(passwordForUsername:);
		_objectRetrieverPassword = self;
		[self updatePingTimerInterval];
		[_serverConnection addObserver:self forKeyPath:@"connected" options:nil context:nil];
		[_serverConnection addObserver:self forKeyPath:@"messageReceived" options:nil context:nil];
		[_serverConnection addObserver:self forKeyPath:@"messageSent" options:nil context:nil];
		[self addObserver:self forKeyPath:@"loggedIn" options:nil context:nil];
		[self addObserver:self forKeyPath:@"secondsDelayPing" options:nil context:nil];
	}
	return self;
}

- (NSString *)passwordForUsername:(NSString *)u { 
	return [NSApp password];
//	id o = [[NSUserDefaults standardUserDefaults] valueForKey:SolarSeekDefaultPassword];
//	return o;
}

- (void)dealloc {
	[_engine removeObserver:self forKeyPath:@"connectionsIdentifier.portListening"];
	[_privileged release];
	[_greeting release];
	[_error release];
	[_admin release];
	[_serverConnection removeObserver:self forKeyPath:@"connected"];
	[_serverConnection removeObserver:self forKeyPath:@"messageReceived"];
	[_serverConnection removeObserver:self forKeyPath:@"messageSent"];
	[_pingTimer release];
	[_serverConnection release];
	[_usernameLoggedIn release];
	[super dealloc];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	if([keyPath isEqual:@"connected"] && object==_serverConnection) {
		if([_serverConnection connected]) {
			[[_engine connectionsIdentifier] startListening];
			[_engine addObserver:self forKeyPath:@"connectionsIdentifier.portListening" options:nil context:nil];
			Message *m = [Message message];
			[m putCode:Login];
			Peer *oldme = [[NSApp engine] me];
			[self willChangeValueForKey:@"username"];
			NSString *oldusername = _usernameLoggedIn;
			_usernameLoggedIn = [[[NSUserDefaults standardUserDefaults] valueForKey:SolarSeekDefaultUsername] retain];
			if([oldusername isEqual:_usernameLoggedIn]==NO) {
				[[NSApp engine] willChangeValueForKey:@"me"];
				Peer *newme = [[[NSApp engine] me] retain];
				[newme setAvailability:[oldme availability]];
				NSDictionary *dict = [oldme dictionaryWithValuesForKeys:
					[NSArray arrayWithObjects:@"privileges",@"availability",
						@"speedAverage",@"countFiles",@"countSlotsAvailable",@"countSlotsTotal",
						@"countDownloadsQueued",@"comment",@"sharesTree",@"address",nil]];
				[newme setValuesForKeysWithDictionary:dict];
				[newme setPicture:[oldme picture]];
				[oldme release];
				[[NSApp engine] didChangeValueForKey:@"me"];
			}
			[oldusername release];
			[self didChangeValueForKey:@"username"];
			NSString *pass = [_objectRetrieverPassword performSelector:_selectorRetrieverPassword withObject:_usernameLoggedIn];
			[m putString:_usernameLoggedIn];
			[m putString:pass];
			[m putInt:185];
			[_serverConnection sendMessage:m];
			m = [Message message];
			[m putCode:SetWaitPort];
			
			[NSApp appendLogWithHeader:@"SERVER"
				HTML:[NSString stringWithFormat:@"Listening for incoming connections on port %d.",[[_engine connectionsIdentifier] portListening]]
				type:LogTypeServer|LogTypeLoud];
			[m putInt:[[_engine connectionsIdentifier] portListening]];
			[_serverConnection sendMessage:m];
		}
		else {
			[_engine removeObserver:self forKeyPath:@"connectionsIdentifier.portListening"];
			[self willChangeValueForKey:@"loggedIn"];
			_loggedIn = NO;
			[self didChangeValueForKey:@"loggedIn"];
			[[_engine connectionsIdentifier] stopListening];
		}
	}
	else if([keyPath isEqual:@"messageReceived"] && object==_serverConnection) {
		Message *m = [_serverConnection messageReceived];
		[self dispatchMessage:m];
	}
	else if((object==_serverConnection && [keyPath isEqual:@"messageSent"]) || 
			object==self && [keyPath isEqual:@"secondsDelayPing"]) {
		Message *m = [_serverConnection valueForKey:@"messageSent"];
		if(object==self)
			[self updatePingTimerInterval];
		[_pingTimer setFireDate:[[m age] addTimeInterval:secondsDelayPing]];
	}
	else if([keyPath isEqual:@"connectionsIdentifier.portListening"] && object==_engine) {
		if([self loggedIn]) {
			unsigned port = [[_engine connectionsIdentifier] portListening];
			Message *m = [Message messageWithCode:SetWaitPort];
			[m putInt:port>0?port:2134];
			[_serverConnection sendMessage:m];
		}
	}
	else if([keyPath isEqual:@"valid"] && object==_privileged) {
		if([_privileged isValid] == NO) {
			Message *m = [Message message];
			[m putCode:CheckPrivileges];
			[_serverConnection sendMessage:m];
		}
	}
	else if([keyPath isEqual:@"loggedIn"] && object==self) {
		if([self loggedIn]) {
			Message *m = [Message message];
			[m putCode:CheckPrivileges];
			[_serverConnection sendMessage:m];
			if([[[NSApp engine] me] availability] == AvailabilityAway)
				[[NSApp engine] setAvailability:AvailabilityAway];
		}
		else {
			[_privileged release];
			_privileged = nil;
		}
	}
	else
		[NSException raise:@"UnknownKeyObservedException" format:@"ServerConnectionController observed unknown value %@ for object %@.",keyPath,object];
}

- (void)updatePingTimerInterval {
	[_pingTimer invalidate];
	_pingTimer = [NSTimer scheduledTimerWithTimeInterval:secondsDelayPing target:self selector:@selector(pingServer:) userInfo:nil repeats:YES];
}

- (void)pingServer:(NSTimer *)timer {
	if([self loggedIn]) {
		Message *m = [Message message];
		[m putCode:Ping];
		[_serverConnection sendMessage:m];
	}
}

- (void)dispatchMessage:(Message *)m {

	[m rewind];
	int code = [m getCode];
	switch(code) {
	case Login:
		[self receiveMessageLogin:m];	//code,byte(success),message(error/greeting),int?(ext ipv4 addr)
		break;
	case AdminMessage:
		[self receiveMessageAdministrativeMessage:m];	//code,str(msg)
		break;
	case CheckPrivileges:
		[self receiveMessagePrivilegesRemaining:m];		//code,int(days)
		break;
	case SecondsBeforePingChildren:
		[self receiveMessageSecondsBeforePingChildren:m]; //code, int(seconds?)
		break;
	default:
		break;
	}
}

- (BOOL)privileged { return [[_privileged info] boolValue]; }
- (NSDate *)privilegedDateExpired { 
	BOOL p = [self privileged];
	return p?[_privileged dateExpired]:nil; 
}
- (BOOL)loggedIn { return _loggedIn; }
- (Availability)availability { return _availability; }
- (NSString *)messageAdministrative { return _admin; }
- (NSString *)messageError { return _error; }
- (NSString *)messageGreeting { return _greeting; }
- (NSString *)username { return _usernameLoggedIn; }

- (void)setAvailability:(Availability)status { 
	[self willChangeValueForKey:@"availability"];
	_availability = status;
	[self didChangeValueForKey:@"availability"];
}


#pragma mark abc

- (void)receiveMessageLogin:(Message *)m {
	char c = [m getByte];
	[self willChangeValueForKey:@"messageGreeting"];
	[self willChangeValueForKey:@"messageError"];
	[self willChangeValueForKey:@"loggedIn"];
	[_greeting release];
	[_error release];
	if(c==1) {	//success
		_greeting = [[m getString] retain];
		_error = nil;
		NSLog(@"%@",_greeting);
		SolarSeekAddress *addy=nil;
		if([m countBytesRemaining]>=4) {
			int a = [m getInt];
			addy = [[SolarSeekAddress alloc] initWithAddressFromInt:a port:[[[NSApp engine] serverConnection] portLocal]];
		}
		else {
			addy = [[SolarSeekAddress alloc] initWithAddress:[[[NSApp engine] serverConnection] addressLocal] port:[[[NSApp engine] serverConnection] portLocal]];
		}
		[[[NSApp engine] me] setAddress:addy];
		[addy release];
	}
	else {	//fail
		_greeting = nil;
		_error = [[m getString] retain];
		NSLog(@"%@",_error);
	}
	_loggedIn = (_error == nil);
	[self didChangeValueForKey:@"messageGreeting"];
	[self didChangeValueForKey:@"messageError"];
	[self didChangeValueForKey:@"loggedIn"];
}

- (void)receiveMessageAdministrativeMessage:(Message *)m {
	[self willChangeValueForKey:@"messageAdministrative"];
	[_admin release];
	_admin = [[m getString] retain];
	[self didChangeValueForKey:@"messageAdministrative"];
}

- (void) receiveMessagePrivilegesRemaining:(Message *)m {
	int days = [m getInt];
	NSNumber *n;
	NSCalendarDate *d=nil;
	[self willChangeValueForKey:@"privileged"];
	[self willChangeValueForKey:@"privilegedDateExpired"];
	[_privileged release];
	if(days > 0) {
		n = [NSNumber numberWithBool:YES];
		d = [NSCalendarDate calendarDate];
		d = [[NSCalendarDate dateWithYear:[d yearOfCommonEra] month:[d monthOfYear] day:[d dayOfMonth] hour:0 minute:0 second:0 timeZone:[d timeZone]]
			dateByAddingYears:0 months:0 days:days+1 hours:0 minutes:0 seconds:0];
		_privileged = [[TimelyInformation alloc] initWithInfo:n validForSeconds:[d timeIntervalSinceDate:[NSDate date]]];
	}
	else
		_privileged = [[TimelyInformation alloc] initWithInfo:[NSNumber numberWithBool:NO] validForSeconds:60*60*24*2];
	[_privileged addObserver:self forKeyPath:@"valid" options:nil context:nil];
	[self didChangeValueForKey:@"privileged"];
	[self didChangeValueForKey:@"privilegedDateExpired"];
}

- (void)receiveMessageSecondsBeforePingChildren:(Message *)m  {
	//code, int(seconds?)
	[self willChangeValueForKey:@"secondsDelayPing"];
	unsigned f = [m getInt];
	secondsDelayPing = (NSTimeInterval)f;
	[self didChangeValueForKey:@"secondsDelayPing"];
}

@end
